﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using MessageServer.pool;
using MessageServer.utilities;
using MessageServer.logging;

namespace MessageServer.threadpool
{
    public delegate void MSThreadPoolCallback(IGatedTask task);
    public delegate void ExceptionCallback(IGatedTask task, Exception e);

    internal class endthread : IGatedTask
    {
        //if a thread gets this task it ends the thread and returns.
        public object runTask()
        {
            return null;
        }

    }

    internal class MSThread : IPoolableObject
    {
        Thread _realthread;
        IGatedTask _runningtask;
        IGatedTask _endTask;
        AutoResetEvent _event;
        int _id;
        MSThreadPoolCallback _callback;
        ExceptionCallback _eCallback;

        internal MSThread(String prefix, int id)
        {
            _id = id;
            _event = new AutoResetEvent(false);
            _realthread = new Thread(execute);
            _realthread.Name = prefix + " - " + id;
            _realthread.Start();
        }

        internal void runtask(IGatedTask task)
        {
            PerfStat s = new PerfStat();
            s.start("MSThread:runtask");
            _endTask = null;
            _runningtask = task;
            s.checkpoint("MSThread:pulsing");
            s.dumpperf(UtilityLogger.LOG);
        }

        internal void wakeup()
        {
            _event.Set();
        }

        public override void Dispose()
        {
            _endTask = new endthread();
            wakeup();
            waitToComplete();
        }

        internal bool busy { get { return (_runningtask != null); } }
        internal MSThreadPoolCallback callback { set { _callback = value;  } }
        internal ExceptionCallback ecallback { set { _eCallback = value; } }
        internal bool running { get { 
            return (_realthread.ThreadState == ThreadState.WaitSleepJoin) || (_realthread.ThreadState == ThreadState.Running); } }
        internal void waitToComplete()
        {
            _realthread.Join();
        }

        internal bool endtask()
        {
            return ((_endTask != null) && (_endTask is endthread));
        }

        private void taskdone()
        {
            if (_runningtask != null)
            {
                _runningtask = null;
                _callback = null;
                _eCallback = null;
                UtilityLogger.LOG.LogDebug("MSThread:taskdone:releasing back to the pool");
                releaseme();
            }
        }

        internal void execute()
        {
            while (_event.WaitOne())
            {
                if (_runningtask != null)
                {
                    try
                    {
                        _runningtask.runTask(); //TODO: handle exceptions from task
                        if (_callback != null)
                            _callback(_runningtask);
                    }
                    catch (Exception e)
                    {
                        UtilityLogger.LOG.LogError(e, e.Message);
                        Console.WriteLine(e.Message);
                        Console.WriteLine(e.StackTrace);
                        if (_eCallback != null)
                            _eCallback(_runningtask, e);
                    }
                }

                if (endtask())
                {
                    UtilityLogger.LOG.LogDebug("MSThread:execute:Have to end thread. Ending.");
                    discardme(); //discard me first so no one else picks me up.
                    //maybe release is not reqd.
                    _runningtask = null;
                    break;
                }
                taskdone();
            }
        }
    }

    internal class MSThreadCreator : IPoolableObjectCreator
    {
        int _threadscreated = 0;
        String _prefix = "Thread";

        internal MSThreadCreator(String poolname)
        {
            _prefix = poolname;
        }

        public IPoolableObject create()
        {
            int val = Interlocked.Increment(ref _threadscreated);
            MSThread thrd = new MSThread(_prefix, val);
            return thrd;
        }
    }

    internal class DummyTask : IGatedTask
    {
        internal DummyTask()
        {
        }

        public object runTask()
        {
            return null;
        }
    }

    public class QueuedObject
    {
        internal IGatedTask _task;
        internal MSThreadPoolCallback _callback;
        internal ExceptionCallback _eCallback;

        internal bool dummy { get { return (_task is DummyTask); } }
    }

    public class QueuedWakeup
    {
        internal MSThread _thread;
        internal bool dummy { get { return (_thread == null); } }
    }

    public class MSThreadPool : IDisposable
    {
        StrictObjectPool _pool;
        MSThreadCreator _creator;
        bool _valid = true;
        TSList<QueuedObject> _queuedTask;
        Executor _runQueue;
        object _flagQueued = new object();
        TSList<QueuedWakeup> _wakeup;
        String _name;

        public MSThreadPool(String nm, int wait, int min, int max)
        {
            _creator = new MSThreadCreator(nm);
            _pool = new StrictObjectPool(_creator, wait, min, max);
            QueuedObject dummy = new QueuedObject();
            dummy._task = new DummyTask();
            _queuedTask = new TSList<QueuedObject>(dummy);
            _runQueue = new AsynchronousExecutor();
            _runQueue.execute(runQueue);
            _wakeup = new TSList<QueuedWakeup>(new QueuedWakeup());
            _name = nm;
        }

        public void schedule(IGatedTask task, MSThreadPoolCallback cb, ExceptionCallback ecb)
        {
            if (_valid)
            {
                PerfStat stat = new PerfStat();
                stat.start("MSThreadPool:schedule:");
                PoolObject obj = null;
                //this is the next task, so run it. Else just queue it.
                if (_queuedTask.empty)
                {
                    stat.checkpoint("MSThreadPool:schedule:" + _name + "getobject");
                    obj = _pool.get();
                    stat.checkpoint("MSThreadPool:schedule:" + _name + "getobject");
                }
                if (obj != null) //if available just run now itself. Forget queuing
                {
                    stat.checkpoint("MSThreadPool:schedule:" + _name + "runtask");
                    runNow(obj, task, cb, ecb);
                    stat.checkpoint("MSThreadPool:schedule:" + _name + "from runNow");
                }
                else
                {
                    stat.checkpoint("MSThreadPool:schedule:queueing");
                    UtilityLogger.LOG.LogDebug("MSThreadPool:schedule:" + _name + "Did not get a thread. Queueing.");
                    QueuedObject tsk = new QueuedObject();
                    tsk._task = task;
                    tsk._callback = cb;
                    tsk._eCallback = ecb;
                    _queuedTask.push(tsk);
                    /*lock (_flagQueued)
                    {
                        Monitor.Pulse(_flagQueued); //set always, but then if the thread comes back and executes it is fine
                    }*/
                    stat.checkpoint("MSThreadPool:schedule:donequeueing");
                }
                stat.dumpperf(UtilityLogger.LOG);
            }
        }

        private void runNow(PoolObject obj, IGatedTask task, MSThreadPoolCallback cb, ExceptionCallback ecb)
        {
            PerfStat s = new PerfStat();
            s.start("MSThreadPool:runNow:");
            UtilityLogger.LOG.LogDebug("MSThreadPool:runNow: Running the task now. " + _name + ":" + task);
            MSThread thread = obj.poolobject as MSThread;
            if (thread.running)
            {
                thread.callback = cb;
                thread.ecallback = ecb;
                thread.runtask(task);
                QueuedWakeup wk = new QueuedWakeup();
                wk._thread = thread;
                _wakeup.push(wk);
            }
            s.dumpperf(UtilityLogger.LOG);
        }

        public void runQueue()
        {
            UtilityLogger.LOG.LogDebug("MSThreadPool:runQueue:Started waiting for queued tasks");

            while (true)
            {
                /*lock (_flagQueued)
                {
                    Monitor.Pulse(_flagQueued);
                }*/
                while (!_queuedTask.empty)
                {
                    UtilityLogger.LOG.LogDebug("MSThreadPool:runQueue:There is a queued task");
                    PoolObject obj = _pool.get();
                    if (obj != null) //if no thread is present, continue polling for a thread
                    {
                        QueuedObject tsk = _queuedTask.pop();
                        if (!tsk.dummy)
                        {
                            UtilityLogger.LOG.LogDebug("MSThreadPool:runQueue:A thread got from pool.. Executing task");
                            //here task will execute and get released
                            runNow(obj, tsk._task, tsk._callback, tsk._eCallback);
                        }
                        else
                        {
                            UtilityLogger.LOG.LogDebug("MSThreadPool:runQueue: For some reason this has become a dummy task why?");
                            _pool.release(obj); //otherwise release back to pool for reuse
                        }
                    }
                    else
                        Thread.Sleep(100); //try again after 100ms. So there possible can be a delay of 100ms
                }

                QueuedWakeup wk = _wakeup.pop();
                while ((wk != null) && (!wk.dummy))
                {
                    wk._thread.wakeup();
                    wk = _wakeup.pop();
                }

                Thread.Sleep(10);
            }
        }

        /**
         * Overloaded convienience method
         * */
        public void schedule(IGatedTask task, MSThreadPoolCallback cb)
        {
            schedule(task, cb, null);
        }

        /**
         * Overloaded convienience method
         * */
        public void schedule(IGatedTask task)
        {
            schedule(task, null, null);
        }

        public void Dispose()
        {
            //need to gate endthread tasks here.
            _valid = false;
            _pool.Dispose();
        }

    }
}
